
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

//#include "plcgw_cfg.h"
#include "file_util.h"
//#include "mk_util.h"
// #include "crc16.h"
// #include "main.h"
// #include "plc_ota.h"


// #include "sys_msg_process.h"
// #include "kt_breaker_protocol.h"
// #include "err_code_mk.h"
// #include "device_manager.h"
// #include "device_operate.h"
// #include "app_manager.h"

#include "device_data_struct.h"

//#include "pb_kt_ota_device.h"
#include "curl_http.h"
#include "err_code_mk.h"
#include "iotv30_config.h"
#include "plcgw_cfg.h"
#include "plcgw_sys.h"
#include "print_log.h"
#include "proto_plat.h"
#include "sys_msg_process.h"

#include "CRC_Check.h"
#include "uart_printf.h"

#include "ota_manager.h"


#define  ota_log_debug  logout

//#define  iotv30_ota_CRC16  CRC16_modbus_big_end_large        //测试通过
#define  iotv30_ota_CRC16  CRC16_modbus_table16_big_end        //测试通过
//#define  iotv30_ota_CRC16  CRC16_modbus_table16_little_end

/////download file crc err, calc_crc=0x718b, rec_crc=0x8b71


/****************************************************/
static ota_param_t ota_param;


static  char *ota_dir_tab[]={
	"",
	OTA_GW_DIR,
	OTA_DEVICE_DIR,
	OTA_CCO_DIR,
	OTA_STA_DIR
};

#if 0
static  char *ota_filename_tab[]={
	"",
	FILENAME_GW,
	FILENAME_DEVICE,
	FILENAME_CCO,
	FILENAME_STA
};
#endif


#if 0
static int down_progress_func(char *progress_data,
                     double t, /* dltotal */
                     double d, /* dlnow */
                     double ultotal,
                     double ulnow)
{
	static double last_get = -1.0;
	if(d != last_get && t!=0) {
		last_get = d;

 // 		log_notice("......%s %g/%g (%g%%)\n", progress_data, d, t, d*100.0/t);  //1.03885e+06/2.05956e+06 (50.4403%)
  		log_notice("......%s %d/%d (%g%%)\n", progress_data, d, t, d*100.0/t);
		if(ota_param.kt_download_cb) {
			ota_param.kt_download_cb((unsigned int)t, (unsigned int)d, OTA_STAGE_GW_DOWNLOAD);
		}
	}
  	return 0;
}

#endif

////计算文件传输进度的方法---curl_http_download()的回调函数 (progress_cb) 

static int down_progress_func(char *progress_data,
                     double dltotal, /* dltotal =download_total*/
                     double dlnow, /* dlnow =download_now */
                     double ultotal,  //=upload_total
                     double ulnow)    //=upload_now
{

	//static double last_get = -1.0;

	static unsigned long last_get = 0;

	 unsigned long ul_total =(unsigned long) dltotal;
	 unsigned long ul_now =(unsigned long) dlnow;


  // LOG_NAME();

	if( ul_now != last_get && ul_total ) ////??????
	{
		last_get = ul_now;

 // 		log_notice("......%s %g/%g (%g%%)\n", progress_data, dlnow, dltotal, dlnow*100.0/dltotal);  //1.03885e+06/2.05956e+06 (50.4403%)
  		log_notice("%s......%s %d/%d (%g%%)\n", __FUNCTION__,progress_data, (unsigned int)dlnow, (unsigned int)dltotal, dlnow*100.0/dltotal);   //这里执行了
	    ota_param.per = (unsigned char)(dltotal*100/dlnow);
		if(ota_param.kt_download_cb) {
		//	log_notice("ota_param-callback\n"); ////这里执行了
			ota_param.kt_download_cb((unsigned int)dltotal, (unsigned int)dlnow, OTA_STAGE_GW_DOWNLOAD);//这里也执行了		
           ////int download_progress_callback(unsigned int total, unsigned int gotten, int stage)
		}
	}

  	return 0;
}



// down_progress_func......*****###  75574/2071515 (3.64825%)
// ota_param-callback
// download callback: not report now
// down_progress_func......*****###  96394/2071515 (4.65331%)
// ota_param-callback
// download callback: not report now
// down_progress_func......*****###  1737570/2071515 (83.8792%)
// ota_param-callback




static void *ota_download_thread(void* arg)
{
	pthread_detach(pthread_self());              //设置该线程为分离状态，不需要主线程回收其系统资源。
	log_debug("ota_download_thread .....\n");

	ota_param_t *ota_param_in = (ota_param_t*)arg;
	memcpy(&ota_param, ota_param_in, sizeof(ota_param_t));

	print_ota_param(&ota_param);
	
	char *ota_target=NULL;
	char *ota_fname=NULL;
	int tab_idx = ota_param.fw_type;
	if(tab_idx>0 && tab_idx<=4) {
		ota_target = ota_dir_tab[tab_idx];
		#if 0
		if(tab_idx == 1) {
			ota_fname =  ota_param.real_name;  //ota_filename_tab[tab_idx];
		}
		else {
			ota_fname =  ota_filename_tab[tab_idx];
		}
		#else
		ota_fname =  ota_param.real_name;
		#endif
	}
	else {
		log_debug("fw type error, shoud: 0< fw_type <=4");
		goto _err_exit;
	}
	snprintf(ota_param.file_name, sizeof(ota_param.file_name)-1, "%s%s%s#%s", APP_DATA_PATH, ota_target, ota_fname, ota_param.fw_ver);
	
	if(curl_http_download(ota_param.url, ota_param.file_name, down_progress_func, 1, ota_param.fw_size)==0) 
	{
		//push_sysmsg_data(MSG_DOWNLOAD_OK, ota_param.file_name, strlen(ota_param.file_name));
		ota_burn_flash( );
		log_debug( "ota %s DOWNLOAD_OK\n",ota_param.file_name );

	}else {//升级异常结束
		ota_error_report(DOWNLOAD_FAIL, OTA_STAGE_GW_DOWNLOAD, ota_param.per);
	}
	
	
_err_exit:	
	log_debug("download thread exit.....");
	pthread_exit(NULL);
	return NULL;
}


int ota_task_init(ota_param_t *param)
{
	pthread_t tid_plc_rx;
	pthread_create(&tid_plc_rx, NULL, ota_download_thread, param);
	return 0;
}


void print_ota_param(const ota_param_t *param)
{

#if 0
	log_info("url=%s, fw_ver=%s, size=%d, crc=0x%04x, fw_type=%d, node_type=0x%04x, name=%s", 
		param->url,	param->fw_ver, param->fw_size, param->fw_crc, param->fw_type,
		param->node_type, param->real_name);
#endif
	ota_log_debug("url=%s, fw_ver=%s, size=%d, crc=0x%04x, fw_type=%d, node_type=0x%04x, name=%s\n", 
		param->url,	param->fw_ver, param->fw_size, param->fw_crc, param->fw_type,
		param->node_type, param->real_name);



	//log_hex_title("node mac:", param->un_mac.oct, 6, 6);
	ota_log_debug("node mac:");
	PrintHex((uint8_t*)param->un_mac.oct,8);

}

/**
burn_one_node() 烧录一个节点mcu
return : 0 , success
         other , fail
***/
static int burn_one_node(const unsigned char *pmac, int dev_type, const unsigned char *pfw, int fw_len,
   const char *file_name)
{
	int res = 0;

#if 0


	// read flash size
	unsigned short chkcrc = crc16_modbus(pfw, fw_len);
	int flash_size;
	if(is_roadlamp(dev_type) ) {  // old ota protocol
	  	flash_size = kt_roadlamp_cmd_read_ota_flash_size_pro(pmac,dev_type);  
	}
	else {   // new breaker
		flash_size = ktb_cmd_read_ota_flash_size_pro(pmac, file_name, fw_len, chkcrc);
	}
	if(flash_size <= 0) {
		ota_log_debug("read flash size err2:burn_one_node()\n");
		ota_error_report(NODE_OFFLINE, OTA_STAGE_TRANSMIT_NODE);
		return NODE_OFFLINE;
	}
	else {
		if(flash_size <= fw_len) {
			ota_error_report(FW_SIZE_TOO_LARGE, OTA_STAGE_TRANSMIT_NODE);
			return FW_SIZE_TOO_LARGE;
		}
	}

	// transmit file
	if (is_roadlamp(dev_type)) {
		res = kt_roadlamp_send_fw_to_node(pmac, dev_type, pfw, fw_len);
	}
	else {
		res = ktb_send_fw_to_node(pmac, pfw, fw_len);
	}
	
	if(!res) {
		// start burn (send crc code, mcu check and then start brun auto)
		ota_log_debug("trasmint fw ok, start burn flash!");
		//unsigned short chkcrc = crc16_modbus(pfw, len);
		if (is_roadlamp(dev_type)) {
			res = kt_roadlamp_cmd_chk_fw_and_burn_pro(pmac, dev_type);	
		}
		#if 0
		else {
			res = ktb_cmd_chk_fw_and_burn_pro(pmac, fw_len, chkcrc);
		}
		#endif
		if(res) {
			ota_error_report(CHKSUM_ERR, OTA_STAGE_NODE_CHK);
		}
		else {
			ota_error_report(0, OTA_STAGE_DEVICE_BURN);
		}
		
	}
	else {
		ota_error_report(TRANSMIT_FW_TO_NODE_ERR, OTA_STAGE_TRANSMIT_NODE);
	}

#endif	

	return res;
}

/******************************************************************************
function: burn_mcu()  烧录mcu固件
param:  *pfw :  固件数据
        len ：   固件长度
        file_name ： 文件名
********************************************************************************/
static int burn_mcu(const unsigned char *pfw, int len, const char *file_name)
{

#if 0

	ota_log_debug("burn fileName:%s\n", file_name);
	int wh_num=get_whitelist_num();
	const device_msg_t *dev_tab = get_dev_msg_tab();
	int burn_num;
	int idx_start;
	unsigned char ok_tab[MAX_WHITELIST_NUM]={0};
	int broadcast_flag = 0;
	int ret = 0;
	if(ota_param.un_mac.num64 == 0xffffffffffff) {
		burn_num = wh_num;
		idx_start = 0;
		broadcast_flag = 1;
	}
	else  {
		ret = ERR_MK;
		burn_num = 1;
		idx_start = get_whitelist_idx(ota_param.un_mac.oct);
		if(idx_start < 0) {
			ota_error_report(NO_DEVICE, OTA_STAGE_TRANSMIT_NODE);
			return NO_DEVICE;
		}
		else {
			dev_tab += idx_start;
			unsigned short sh_type;
			sh_type = get_sh_type_from_dev_type(dev_tab->dev_type);
			if(sh_type == ota_param.node_type)	{
				ret = burn_one_node(dev_tab->addr_un.addr_st.node_mac, sh_type, pfw, len, file_name);
				if(ret == 0) {
					log_hex_title("burn success:", dev_tab->addr_un.addr_st.node_mac, 6, 6);
				}
				else {
					log_hex_title("burn error:", dev_tab->addr_un.addr_st.node_mac, 6, 6);
				}
			}
			else {
				log_err("device type=0x%x, but fwFile type is 0x%x", sh_type, ota_param.node_type);
				ret = OTA_FW_NOT_MATCH_DEVICE_TYPE;
			}
		}
		return ret;
	}

	int i;
	int done_num = 0;
	dev_tab += idx_start;
	for(i=idx_start; i<MAX_WHITELIST_NUM; i++) {
		unsigned short sh_type;
		sh_type = get_sh_type_from_dev_type(dev_tab->dev_type);
	
		if(sh_type !=0 ) {
			done_num++;
		}
		if(sh_type == ota_param.node_type) {
			if(burn_one_node(dev_tab->addr_un.addr_st.node_mac, sh_type, pfw, len, file_name)) {
				log_hex_title("burn error:", dev_tab->addr_un.addr_st.node_mac, 6, 6);
			}
			else {
				ok_tab[i] = 1;
				log_hex_title("burn success:", dev_tab->addr_un.addr_st.node_mac, 6, 6);
			}
			
			if(done_num >= burn_num) {
				break;
			}
		}
	
		
		dev_tab++;
	}

#endif

	return 0;
}

const ota_file_dir_t file_dir_tab[]={
	{".sh", APP_PATH},
	{".png", APP_DATA_PATH},
	{".csv", CFG_FILE_PATH},
	{".ini", CFG_FILE_PATH},	
	{".hupg", STA_OTA_FILE_PATH},
	{NULL, NULL}
};
/*********************************************************
脚本 *.sh 放在 ./
配置文件  *.csv, *.ini 放到    ./data/cfg_dat/

************************************************/
int gw_cfg_file_process(const char *fullname, const char *realname)
{
	ota_log_debug("get file:%s, realname=%s", fullname, realname);
	char dst_file_name[128]={0};

	int len = strlen(realname);
	if(len < 5) {
		return -1;
	}

	const char *pext = get_file_name_ext(realname);
	printf("input file:%s, ext=%s\n", realname, pext);
	
	const ota_file_dir_t *pfile_tab = file_dir_tab;
	while(pfile_tab->file_name_ext) {
		printf("fileExt:%s, path=%s\n", pfile_tab->file_name_ext, pfile_tab->file_path);
		int cmplen = strlen(pfile_tab->file_name_ext);

		if(strcmp(pext,  pfile_tab->file_name_ext)==0) {
			printf("is %s file\n", pext);
			snprintf(dst_file_name, sizeof(dst_file_name)-1, "%s%s", pfile_tab->file_path, realname);
		    break;
		}
		pfile_tab++;
	}
	if(*dst_file_name == 0) {
		log_info("ota file: %s invalid!\n", realname);
		return -1;
	}
	
	ota_log_debug("copy file: %s from:%s\n", dst_file_name, fullname);

	int ret = copy_file(dst_file_name, fullname);
	

	return ret;
}


//////生成update_file.txt ，同步信息到 otaservo 进程
int update_file_process(const char *filename)
{
	int ret = -1;
	//char file_path[64];
	int buf_len = 256;
	char *file_path = malloc(buf_len);
	if(file_path == NULL) {
		return -1;
	}

	LOG_NAME();

	printf("file_name:%s\n", filename);
	
	sprintf(file_path, "%s%s", APP_DATA_PATH, UPDATE_FILE_NAME);////生成update_file.txt ，同步信息到 otaservo 进程
	FILE *fp = fopen(file_path, "w");
	if(fp) {
		fwrite(filename, 1, strlen(filename), fp);
		fclose(fp);
		printf("update gw bin now\n");

		fp = fopen(file_path, "r");
		if(fp == NULL) {
			goto _err_exit;
		}

		
		//char dat_buf[64]={0};
		//int len = 64;
		memset(file_path, 0, buf_len);
		fgets(file_path, buf_len, fp);
		if(strcmp(file_path, filename)==0) {
			printf("wr file name ok\n");
			ret = 0;
		}

		fclose(fp);
	}
		
_err_exit:

	if(file_path) {
		free(file_path);
		file_path=NULL;	
	}
	return ret;
}









////ota_burn_flash_pro
//void ota_burn_flash(const ota_param_t *pmsg)
void ota_burn_flash(void )
{
	LOG_NAME();
	// chk file len and crc

	// len
	if(ota_param.fw_size != read_file_len(ota_param.file_name)) {
		ota_log_debug("download file len err fw_size=%d\n",ota_param.fw_size);
		return;
	}

	unsigned char *buf = malloc(ota_param.fw_size);
	if(buf == NULL) {
		perror("malloc ota_burn_flash fail\n");
		return;
	}

	FILE *fp = fopen(ota_param.file_name,"r");
	if(fp == NULL) {
		ota_log_debug("open ota file:%s error\n", ota_param.file_name);

		free(buf);
		buf = NULL;		
		return;  
	}

	int read_len = fread(buf, 1, ota_param.fw_size, fp);
	if(read_len != ota_param.fw_size) {
		ota_log_debug("read ota file err:%s,fw_size=%d,read_len=%d\n", ota_param.file_name,ota_param.fw_size,read_len);
		goto  __ota_burn_flash_exit ;
	}else {
		ota_log_debug("read ota file ok:%s, read_len=%d\n", ota_param.file_name,ota_param.fw_size);
	}


	uint16_t chk_crc;//

//	chk_crc.num = crc16_modbus(buf, ota_param.fw_size);	
//	chk_crc = CRC16_modbus_big_end(buf, ota_param.fw_size);

	chk_crc = iotv30_ota_CRC16(buf, ota_param.fw_size);


	if(chk_crc == ota_param.fw_crc) 
	{///-----校验ok---
        //ota_param.per = 100;
		log_notice("download file crc ok, calc_crc=0x%04x\n", chk_crc );
	    ota_error_report(OTA_ERR_CODE_SUCCESSED, OTA_STAGE_GW_CHK,100);    //上报升级状态


//		switch (ota_param.node_type) 	
		switch ( ota_param.fw_type ) 		
		{
			case OTA_C1_6_FW_TYPE_HOST :   // gw DEV_TYPE_GATEWAY
		    {

			  /***
                ‘plcgw’打头,‘  gw_’打头 为网关运行文件， 其他为配置文件
			  ****/
				log_notice("burn gw fw");

				////必须以  "plcgw" 或者 "gw_" 开头   
				if( strncmp(ota_param.real_name, "plcgw", 5)==0 || \
					strncmp(ota_param.real_name, "gw_", 3)==0)
				{
					#if 0
					    send_update_app_cmd(ota_param.file_name);
					#else

                        ////生成update_file.txt ，同步信息到 otaservo 进程
						if( update_file_process(ota_param.file_name)==0) 
						{
							log_notice("set update file ok############\n");

							fclose(fp);
							free(buf);
							buf = NULL;

							sleep(5);

				            ota_error_report(OTA_ERR_CODE_SUCCESSED, OTA_STAGE_GW_SAVE ,100);  //OTA_STAGE_FINISH

							system_reset();
							
						}else {
							log_notice("set update file err..............\n");
							ota_error_report(OTA_ERR_CODE_FAILED, OTA_STAGE_GW_SAVE ,100); 
						}

					#endif			

				}else {

					// if is plc sta file ?
					const char *pext = get_file_name_ext(ota_param.real_name);
					printf("get file:%s, ext=%s\n", ota_param.real_name, pext);

					if(strcmp(pext, ".hupg") == 0)
					{
						printf("is plc sta ota file! start ota plc sta ......\n");
						// device_addr_un_t addr_un;
						// set_mac_broadcast(&addr_un);
						// plc_node_ota_full_auto_create_thread(addr_un.addr_st.node_mac, 
						// 	ota_param.file_name, download_progress_callback, ota_error_report);

					}else {

						if( gw_cfg_file_process(ota_param.file_name, ota_param.real_name)==0 )
						{
							log_notice("cp file:%s ok", ota_param.real_name);
						}else {
							log_notice("cp file:%s error", ota_param.real_name);
						}
					}
				}
				
			}  break;

			default:   // invalid
			{
				log_info("ota fw_type:0x%04x not support\n", ota_param.fw_type);
			}	break;
		}

	}else {////校验失败

		log_err("download file crc err, calc_crc=0x%04x, rec_crc=0x%04x\n", chk_crc, ota_param.fw_crc );
		//ota_error_report(CHKSUM_ERR, OTA_STAGE_GW_CHK);
		ota_error_report(OTA_ERR_CODE_FAILED, OTA_STAGE_GW_CHK ,ota_param.per);		
	}


__ota_burn_flash_exit:

   if(NULL !=fp)
   {
	  fclose(fp);
   }


   if( NULL !=buf)
   {
	   free(buf);
	   buf = NULL;
   }


}








////ota_param_t
void ota_burn_flash_pro(const sys_main_msg_t *pmsg)
{
	ota_log_debug("ota_burn_flash_pro");
	// chk file len and crc

	if( pmsg->dat_len> sizeof(ota_param.file_name) ) 
	{
		ota_log_debug("file name err");
		return;
	}

	if(memcmp(ota_param.file_name, pmsg->msg_dat, pmsg->dat_len)) {
		ota_log_debug("file name err");
		return;
	}

	if(ota_param.file_name[pmsg->dat_len]!=0) {
		ota_log_debug("file name end err");
		return;
	}

	// len
	if(ota_param.fw_size != read_file_len(ota_param.file_name)) {
		ota_log_debug("download file len err");
		return;
	}

	unsigned char *buf = malloc(ota_param.fw_size);
	if(buf == NULL) {
		perror("malloc ota_burn_flash_pro");
		return;
	}

	FILE *fp = fopen(ota_param.file_name,"r");
	if(fp == NULL) {
		ota_log_debug("open ota file:%s error", ota_param.file_name);
		return;
	}

	int read_len = fread(buf, 1, ota_param.fw_size, fp);
	if(read_len != ota_param.fw_size) {
		ota_log_debug("read ota file err:%s ", ota_param.file_name);
		return;
	}
	else {
		ota_log_debug("read ota file ok:%s!\n", ota_param.file_name);
	}

	// crc
//	un_short_t chk_crc;//
	uint16_t chk_crc;//


//	chk_crc.num = crc16_modbus(buf, ota_param.fw_size);	
	chk_crc = CRC16_modbus_big_end(buf, ota_param.fw_size);

	if(chk_crc == ota_param.fw_crc) {
		// burn
		int dev_type;
		if(ota_param.node_type) {
			dev_type = ota_param.node_type;
		}
		else {
			dev_type = DEV_TYPE_GATEWAY;
		}
		switch (dev_type) {
			case	DEV_TYPE_GATEWAY:   // gw
			  /***
                ‘plcgw’打头,‘  gw_’打头 为网关运行文件， 其他为配置文件
			  ****/
				log_notice("burn gw fw");
				ota_error_report(0, OTA_STAGE_FINISH, 100);
				if(strncmp(ota_param.real_name, "plcgw", 5)==0 || 
					strncmp(ota_param.real_name, "gw_", 3)==0) {
					#if 0
					send_update_app_cmd(ota_param.file_name);
					#else
					if( update_file_process(ota_param.file_name)==0) {
						log_notice("set update file ok#######################\n");
						sleep(5);
						system_reset();
						
					}
					else {
						log_notice("set update file err.............................\n");
					}
					#endif
				}
				else {
					// if is plc sta file ?
					const char *pext = get_file_name_ext(ota_param.real_name);
					printf("get file:%s, ext=%s\n", ota_param.real_name, pext);
					if(strcmp(pext, ".hupg") == 0) {
						printf("is plc sta ota file! start ota plc sta ......\n");
						// device_addr_un_t addr_un;
						// set_mac_broadcast(&addr_un);
						// plc_node_ota_full_auto_create_thread(addr_un.addr_st.node_mac, 
						// 	ota_param.file_name, download_progress_callback, ota_error_report);
					}
					else {
						if (gw_cfg_file_process(ota_param.file_name, ota_param.real_name)==0 ) 
						{
							log_notice("cp file:%s ok", ota_param.real_name);
						}else {
							log_notice("cp file:%s error", ota_param.real_name);
						}
					}
				}
				break;

#if 0
			case	DEV_TYPE_ROADLAMP:   // mcu
			case	DEV_TYPE_ROADLAMP_2COLOR:   // mcu
			case	DEV_TYPE_RL_2COLOR:   // mcu
			case	DEV_TYPE_KT_BREAKER:
			case	DEV_TYPE_KT_BREAKER_2:
			case	DEV_TYPE_KT_BREAKER_3PH:
				log_info("burn mcu");
				setup_ota_node_flag(1);
				int ret = burn_mcu(buf, ota_param.fw_size, ota_param.real_name);
				if( ret ){
					ota_error_report(ret, OTA_STAGE_DEVICE_BURN,99);
				}
				setup_ota_node_flag(0);
				
				break;
			case	2:   // cco
				log_info("burn cco");
				break;
			case	3:   // sta
				log_info("burn sta");
				break;

#endif
			default:   // invalid
				log_info("invalid ota:x%04x", ota_param.node_type);
				break;
		}

	}
	else {
		log_err("download file crc err, calc_crc=0x%04x, should_crc=0x%04x", chk_crc, ota_param.fw_crc );	
		ota_error_report(CHKSUM_ERR, OTA_STAGE_GW_CHK,99);
	}


	fclose(fp);

	free(buf);
	buf = NULL;

}


static int ota_node_flag;
void setup_ota_node_flag(int flag)
{
	ota_node_flag= flag;
}

int read_ota_node_flag()
{
	return ota_node_flag;
}


int  at_ota_node(const char *mac_str, const char *file_name)
{
#if 0
	setup_ota_node_flag(1);
	int ret = burn_mcu(buf, ota_param.fw_size, ota_param.real_name);
	if( ret ){
		ota_error_report(ret, OTA_STAGE_DEVICE_BURN);
	}
	setup_ota_node_flag(0);
#endif

	return 0;
}


